golang中http server设置的几种方式记录

1.1 最简单的方式

通过查看http.ListenAndServe的源码可以知道,其第一个参数是监听地址,第二个参数是一个Handler接口类型, 只要是实现了这个接口的ServeHTTP函数即可。这里的http.ListenAndServe函数会创建一个server结构体实例, 并将server的Handler属性赋值为我们手动实现的Handler接口。

1
2
3
4
5
6
7
8
9
10
11
12
// 这里aa实现了http.Handler接口,便可以直接通过该接口返回
// 但是这里如果在ServeHTTP中直接处理, 这个函数会变得异常庞大
type aa struct {
}

func (a aa) ServeHTTP(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("hhhhhh"))
}

func test1() {
http.ListenAndServe(":8080", aa{})
}
1
2
3
4
5
6
7
8
9
10
//http.ListenAndServe函数说明
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}

// http.Handler接口说明
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

1.2 自定义server

1.1的方式调用http.ListenAndServe会默认创建一个server, 但是这样的server, 我们不能更好的控制server的其他参数,如超时等
server 启动过函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type aa struct {
}

func (a aa) ServeHTTP(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("hhhhhh"))
}

//最基本的方式是自己初始化一个http.Server结构体, 初始化一个Handler接口
// 这里可以自己设置超时参数
func test2() {
// 这里创建了一个新的server, 可以设置超时

s := http.Server{
Addr: ":8081",
Handler: aa{}, //处理函数赋值
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}

s.ListenAndServe()
}

这里可以再看看http.Server的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil

// TLSConfig optionally provides a TLS configuration for use
// by ServeTLS and ListenAndServeTLS. Note that this value is
// cloned by ServeTLS and ListenAndServeTLS, so it's not
// possible to modify the configuration with methods like
// tls.Config.SetSessionTicketKeys. To use
// SetSessionTicketKeys, use Server.Serve with a TLS Listener
// instead.
TLSConfig *tls.Config

// ReadTimeout is the maximum duration for reading the entire
// request, including the body.
//
// Because ReadTimeout does not let Handlers make per-request
// decisions on each request body's acceptable deadline or
// upload rate, most users will prefer to use
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout time.Duration

// ReadHeaderTimeout is the amount of time allowed to read
// request headers. The connection's read deadline is reset
// after reading the headers and the Handler can decide what
// is considered too slow for the body.
ReadHeaderTimeout time.Duration

// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
// let Handlers make decisions on a per-request basis.
WriteTimeout time.Duration

// IdleTimeout is the maximum amount of time to wait for the
// next request when keep-alives are enabled. If IdleTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, ReadHeaderTimeout is used.
IdleTimeout time.Duration

// MaxHeaderBytes controls the maximum number of bytes the
// server will read parsing the request header's keys and
// values, including the request line. It does not limit the
// size of the request body.
// If zero, DefaultMaxHeaderBytes is used.
MaxHeaderBytes int

// TLSNextProto optionally specifies a function to take over
// ownership of the provided TLS connection when an NPN/ALPN
// protocol upgrade has occurred. The map key is the protocol
// name negotiated. The Handler argument should be used to
// handle HTTP requests and will initialize the Request's TLS
// and RemoteAddr if not already set. The connection is
// automatically closed when the function returns.
// If TLSNextProto is not nil, HTTP/2 support is not enabled
// automatically.
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

// ConnState specifies an optional callback function that is
// called when a client connection changes state. See the
// ConnState type and associated constants for details.
ConnState func(net.Conn, ConnState)

// ErrorLog specifies an optional logger for errors accepting
// connections, unexpected behavior from handlers, and
// underlying FileSystem errors.
// If nil, logging is done via the log package's standard logger.
ErrorLog *log.Logger

disableKeepAlives int32 // accessed atomically.
inShutdown int32 // accessed atomically (non-zero means we're in Shutdown)
nextProtoOnce sync.Once // guards setupHTTP2_* init
nextProtoErr error // result of http2.ConfigureServer if used

mu sync.Mutex
listeners map[*net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}

server 超时参数的跨度图:
server 超时参数的定义

1.3 更具花样的Handler

下面是一个我们常用等一种启动http server等方式,这里主要在实现Handler接口时,做了更多工作,主要是使用了一个多路选择器ServerMux去对不同等url pattern做不同的匹配处理,先看一个最常见的例子,这里未创建新的serveMux,会使用默认的DefaultServeMux:

1
2
3
4
5
6
7
8
9
func h(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("new pattern"))
}
// 这里会对以/test开始的URL请求使用h函数进行处理
func test4() {
http.HandleFunc("/test", h)
// 这里的第二参数是nil, 在处理的时候就会使用 DefaultServeMux
http.ListenAndServe(":8083", nil)
}

说明:
这里的http.HandleFunc的功能是向http包的DefaultServeMux加入新的处理路由。

1
2
3
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}

DefaultServeMux是一个ServeMux结构体的实例,其中有个m属性是一个map类型,存放了不同URL pattern的处理Handler。同时ServeMux也实现了ServeHTTP函数,是http.Handler接口的实现。 其类型定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}

type muxEntry struct {
h Handler
pattern string
}

// ServeMux实现了Handler接口
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}

1.4 使用自定义的serveMux

我们也可以不使用默认的serveMux,去自己初始化一个serveMux, 可以调用http.NewServeMux(),或者直接NewServeMux{}的方式去创建。下面的例子在构建server时,还是使用默认的server, 可以参考1.2节,创建自定义的server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// test3 我们可以增加一个ServeMux来做一个请求路由处理工作
func h(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("new pattern"))
}
func test3() {
mux := http.NewServeMux()

//通过HandleFunc函数去向mux中handler中注册处理函数
mux.HandleFunc("/test", func(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("test3333"))
})

// 直接调用Handle函数去注册处理函数,
// 这里的http.HandleFunc 是一个type类型,这里调用将h转换为一个Handler接口的实现
mux.Handle("/st", http.HandlerFunc(h))

// 这里会使用默认创建的server
//也可以参考1.2创建自定义的server
http.ListenAndServe(":8082", mux)
}

1.6 go-restful中创建http server的方式

网上基于net/http包封装的http 框架有很多, 介绍一个我常用的库,go-restful,k8s的apiserver部分使用了这个库,算是一个比较优秀的rest库了。
在这个框架下,有如下几个基本概念

  • container
  • webservice
  • route

一个container是用来包括一组webservice, 一个webservice包含多个route, 每个route代表了对一个URL pattern的处理, 这里看一个使用默认container的例子

1
2
3
4
5
6
7
8
9
10
11
	// 这里使用默认的container
ws := new(restful.WebService)
ws.Route(ws.GET("/default").To(h))
restful.Add(ws)
go func() {
http.ListenAndServe(":8080", nil)
}()

func h(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "default container")
}

下面再看一个使用自定义container的例子:

1
2
3
4
5
6
7
// container 同样实现了http.Hadler接口
c := restful.NewContainer()
ws2 := new(restful.WebService)
ws2.Route(ws2.GET("/hello").To(hello2))
c.Add(ws2)
server := &http.Server{Addr: ":8081", Handler: c}
log.Fatal(server.ListenAndServe())

这里可以展开看一下container的Add函数,主要就是执行的c.addHandler(service, c.ServeMux)函数,将container的dispatch函数加入到serverMux,作为URL pattern的handler。有兴趣的可以详细去看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func (c *Container) Add(service *WebService) *Container {
c.webServicesLock.Lock()
defer c.webServicesLock.Unlock()

// if rootPath was not set then lazy initialize it
if len(service.rootPath) == 0 {
service.Path("/")
}

// cannot have duplicate root paths
for _, each := range c.webServices {
if each.RootPath() == service.RootPath() {
log.Printf("WebService with duplicate root path detected:['%v']", each)
os.Exit(1)
}
}

// If not registered on root then add specific mapping
if !c.isRegisteredOnRoot {
c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
}
c.webServices = append(c.webServices, service)
return c
}

相关:
net/http处理连接请求的过程分析: